Skip to content

GH#837: fix network activation reliability in multisite setup wizard#852

Merged
superdav42 merged 1 commit into
mainfrom
feature/auto-20260414-093848
Apr 14, 2026
Merged

GH#837: fix network activation reliability in multisite setup wizard#852
superdav42 merged 1 commit into
mainfrom
feature/auto-20260414-093848

Conversation

@superdav42

Copy link
Copy Markdown
Collaborator

Summary

Fixes a race condition in the Multisite Setup Wizard where the final Network Activate Plugin step would sometimes silently fall back to single-site activation instead of network-wide activation.

Root cause

_install_network_activate() in Multisite_Network_Installer called activate_plugin(WP_ULTIMO_PLUGIN_FILE, '', true) to perform network activation. WordPress's activate_plugin() checks is_multisite() internally and uses the active_sitewide_plugins sitemeta key only when multisite is active. If is_multisite() returns false, it falls back silently to single-site activation.

The MULTISITE constant is written to wp-config.php in the immediately-preceding step (_install_update_wp_config). Each wizard step runs in a separate AJAX request, but if OPcache or another PHP bytecode cache serves a stale wp-config.php in the next request, is_multisite() returns false in the network_activate step — producing the "appears to but doesn't, sometimes" behaviour from the issue.

A secondary bug: the early-return guard used is_plugin_active(WP_ULTIMO_PLUGIN_FILE) with the full absolute path instead of the plugin basename (WP_ULTIMO_PLUGIN_BASENAME), so the guard never fired.

Fix

Replace activate_plugin() with a direct write to the wp_sitemeta table, as the existing code comment already described as the intent. This bypasses the is_multisite() dependency entirely and guarantees reliable network activation regardless of bytecode caching.

Files changed

  • EDIT: inc/installers/class-multisite-network-installer.php — rewrites _install_network_activate() to write directly to {prefix}sitemeta via $wpdb->get_row / $wpdb->update / $wpdb->insert, using WP_ULTIMO_PLUGIN_BASENAME throughout
  • NEW: tests/WP_Ultimo/Installers/Multisite_Network_Installer_Test.php — 16 tests covering get_steps(), get_config(), check_network_tables_exist(), and all branches of _install_network_activate() including idempotency and existing-plugin preservation

Verification

WP_TESTS_DIR=/tmp/wordpress-tests-lib vendor/bin/phpunit \
  --filter Multisite_Network_Installer_Test --no-coverage --testdox
# OK (16 tests, 49 assertions)

vendor/bin/phpstan analyse inc/installers/class-multisite-network-installer.php --no-progress
# [OK] No errors

Resolves #837

@superdav42 superdav42 added the origin:worker Auto-created by pulse labelless backfill (t2112) label Apr 14, 2026
@coderabbitai

coderabbitai Bot commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@superdav42 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 39 minutes and 6 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 39 minutes and 6 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b4f2043d-3486-4a24-a097-7f1dae0c46ef

📥 Commits

Reviewing files that changed from the base of the PR and between bbcffc0 and f9ffe08.

📒 Files selected for processing (2)
  • inc/installers/class-multisite-network-installer.php
  • tests/WP_Ultimo/Installers/Multisite_Network_Installer_Test.php
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/auto-20260414-093848

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

…ation

The _install_network_activate() step in Multisite_Network_Installer relied
on activate_plugin() to network-activate the plugin. That function checks
is_multisite() internally and falls back to single-site activation when it
returns false. The MULTISITE constant is written to wp-config.php only in
the immediately-preceding step (_install_update_wp_config); if OPcache or
another PHP bytecode cache serves a stale wp-config.php in the next AJAX
request, is_multisite() returns false and the plugin ends up activated as
a single-site plugin instead of network-wide — matching the 'sometimes
fails' behaviour reported in GH#837.

The fix writes directly to the sitemeta table (as the original code comment
already intended), bypassing activate_plugin() entirely. This guarantees
network activation regardless of whether the current PHP process has loaded
the new multisite constants.

Also fixes the early-return guard which used the full absolute path
(WP_ULTIMO_PLUGIN_FILE) instead of the plugin basename
(WP_ULTIMO_PLUGIN_BASENAME), causing is_plugin_active() to never match.

Adds Multisite_Network_Installer_Test with 16 tests (49 assertions) covering
get_steps(), get_config(), check_network_tables_exist(), and all branches
of _install_network_activate() including idempotency and existing-plugin
preservation.

Fixes #837
@superdav42 superdav42 force-pushed the feature/auto-20260414-093848 branch from 67c8750 to f9ffe08 Compare April 14, 2026 15:50
@superdav42 superdav42 merged commit d8e87dc into main Apr 14, 2026
5 of 7 checks passed
@superdav42

Copy link
Copy Markdown
Collaborator Author

Merge Summary

Issue: #837 — The multisite setup wizard does not network activate the plugin

Root cause: _install_network_activate() in Multisite_Network_Installer called activate_plugin(), which internally checks is_multisite(). The MULTISITE constant is written to wp-config.php only in the preceding step; if OPcache serves a stale copy on the next AJAX request, is_multisite() returns false and the plugin is activated single-site only — explaining the intermittent failure.

A secondary bug: the early-return guard used WP_ULTIMO_PLUGIN_FILE (full absolute path) instead of WP_ULTIMO_PLUGIN_BASENAME, so the guard never fired.

Fix: Replaced activate_plugin() with a direct write to the {prefix}sitemeta table, bypassing the is_multisite() dependency entirely. The implementation matches what the original code comment already described as the intent.

Changes:

  • inc/installers/class-multisite-network-installer.php — rewrote _install_network_activate()
  • tests/WP_Ultimo/Installers/Multisite_Network_Installer_Test.php — 16 new tests (49 assertions)

Verification: All 16 new tests pass; PHPStan reports no errors.


aidevops.sh v3.8.25 plugin for OpenCode v1.4.3 with claude-sonnet-4-6 spent 12m and 1,346 tokens on this as a headless worker. Overall, 1m since this issue was created.

@github-actions

Copy link
Copy Markdown

🔨 Build Complete - Ready for Testing!

📦 Download Build Artifact (Recommended)

Download the zip build, upload to WordPress and test:

🌐 Test in WordPress Playground (Very Experimental)

Click the link below to instantly test this PR in your browser - no installation needed!
Playground support for multisite is very limitied, hopefully it will get better in the future.

🚀 Launch in Playground

Login credentials: admin / password

@github-actions

Copy link
Copy Markdown

🔨 Build Complete - Ready for Testing!

📦 Download Build Artifact (Recommended)

Download the zip build, upload to WordPress and test:

🌐 Test in WordPress Playground (Very Experimental)

Click the link below to instantly test this PR in your browser - no installation needed!
Playground support for multisite is very limitied, hopefully it will get better in the future.

🚀 Launch in Playground

Login credentials: admin / password

@github-actions

github-actions Bot commented Apr 14, 2026

Copy link
Copy Markdown

Performance Test Results

Performance test results for f8c57a4 are in 🛎️!

Note: the numbers in parentheses show the difference to the previous (baseline) test run. Differences below 2% or 0.5 in absolute values are not shown.

URL: /

Run DB Queries Memory Before Template Template WP Total LCP TTFB LCP - TTFB
0 41 37.79 MB 840.00 ms 168.50 ms (-5.50 ms / -3% ) 1050.50 ms 2108.00 ms 1992.45 ms 93.60 ms (+3.15 ms / +3% )
1 56 49.03 MB 968.50 ms (+52.50 ms / +5% ) 148.00 ms 1116.50 ms (+55.00 ms / +5% ) 2106.00 ms (+56.00 ms / +3% ) 2026.45 ms (+63.50 ms / +3% ) 81.60 ms

superdav42 added a commit that referenced this pull request Apr 15, 2026
PR #852 fixed _install_network_activate() to write directly to sitemeta,
but two other code paths still allowed the wizard to report success
without actually network-activating the plugin:

1. ajax_network_activate() on the requirements page still called
   activate_plugin() which silently falls back to single-site activation
   when OPcache serves a stale wp-config.php. Now delegates to
   Multisite_Network_Installer::_install_network_activate() for the
   same direct sitemeta write.

2. Multisite_Setup_Admin_Page::setup_install() was missing an explicit
   exit after wp_send_json_error(), which could allow the response to
   be corrupted if headers were already partially sent.

3. Base_Installer::handle() never checked whether COMMIT succeeded.
   A silently-failed COMMIT would roll back the sitemeta write on
   connection close, but handle() still returned success. Now checks
   the COMMIT return value and returns WP_Error on failure.

Refs #837
superdav42 added a commit that referenced this pull request Apr 15, 2026
PR #852 fixed _install_network_activate() to write directly to sitemeta,
but two other code paths still allowed the wizard to report success
without actually network-activating the plugin:

1. ajax_network_activate() on the requirements page still called
   activate_plugin() which silently falls back to single-site activation
   when OPcache serves a stale wp-config.php. Now delegates to
   Multisite_Network_Installer::_install_network_activate() for the
   same direct sitemeta write.

2. Multisite_Setup_Admin_Page::setup_install() was missing an explicit
   exit after wp_send_json_error(), which could allow the response to
   be corrupted if headers were already partially sent.

3. Base_Installer::handle() never checked whether COMMIT succeeded.
   A silently-failed COMMIT would roll back the sitemeta write on
   connection close, but handle() still returned success. Now checks
   the COMMIT return value and returns WP_Error on failure.

Refs #837
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

origin:worker Auto-created by pulse labelless backfill (t2112)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The multi-site settup wizard does not network activate the plugin

1 participant